﻿
package extremefx.i18n {
	import extremefx.extensions.isNullOrEmpty;

	/**
	 * @author Marcelo Volmaro
	 */
	public class NumberFormatInfo implements IFormatProvider {
		private static const _NATIVE_DIGITS:Vector.<String> = Vector.<String>(["0", "1", "2", "3", "4", "5", "6", "7", "8", "9"]);
    	private static var _invInfo:NumberFormatInfo;
		
		protected var _currencyDecimalDigits:int;
		public var currencyDecimalSeparator:String;
		public var currencyGroupSeparator:String;
		protected var _currencyGroupSizes:Vector.<int>;
		protected var _currencyNegativePattern:int;
		protected var _currencyPositivePattern:int;
		public var currencySymbol:String;
		public var nanSymbol:String;
		
		/**
		 * 0	Context-based substitution. Digits are displayed based on the previous text in the same output:
		 * 		that is, European digits follow Latin scripts, Arabic-Indic digits follow Arabic text, and other national
		 * 		digits follow text written in various other scripts. When there is no preceding text, the locale and the displayed
		 * 		reading order determine digit substitution, as shown in the following table.
		 * 		Locale		Reading order		Digits used
		 * 		Arabic		Right-to-left		Arabic-Indic
		 * 		Thai		Left-to-right		Thai digits
		 * 		All others	Any					No substitution is used.
		 * 1	No substitution is used. This gives full Unicode compatibility.
		 * 2	Native digit substitution. National shapes are displayed according to <code>nativeDigits</code>.
		 */
		public var digitSubstitution:int;
		protected var _nativeDigits:Vector.<String>;
		public var negativeInfinitySymbol:String;
		public var negativeSign:String;
		protected var _numberDecimalDigits:int;
		public var numberDecimalSeparator:String;
		public var numberGroupSeparator:String;
		protected var _numberGroupSizes:Vector.<int>;
		protected var _numberNegativePattern:int;
		protected var _percentDecimalDigits:int;
		public var percentDecimalSeparator:String;
		public var percentGroupSeparator:String;
		protected var _percentNegativePattern:int;
		public var percentSymbol:String;
		public var perMilleSymbol:String;
		public var positiveInfinitySymbol:String;
		public var positiveSign:String;
		protected var _percentPositivePattern:int;
		protected var _percentGroupSizes:Vector.<int>;

		public function NumberFormatInfo(...args) {
			_currencyDecimalDigits =       	2;
			currencyDecimalSeparator =    	".";
			currencyGroupSeparator =      	",";
			_currencyGroupSizes =          	Vector.<int>([3]);
			_currencyNegativePattern =     	0;
			_currencyPositivePattern =     	0;
			currencySymbol =              	"$";
			
			nanSymbol =                   	"NaN";
			negativeInfinitySymbol =      	"-Infinity";
			negativeSign =					"-";
			
			// Number Related Format Info
			_numberDecimalDigits =         	2;
			numberDecimalSeparator =      	".";
			numberGroupSeparator =        	",";
			_numberGroupSizes =            	Vector.<int>([3]);
			_numberNegativePattern =       	1;
			
			// Percent Related Format Info
			_percentDecimalDigits =        	2;
			percentDecimalSeparator =     	".";
			percentGroupSeparator =       	",";
			_percentGroupSizes =           	Vector.<int>([3]);
			_percentNegativePattern =      	0;
			_percentPositivePattern =      	0;
			percentSymbol=                	"%";
			
			perMilleSymbol =              	"‰";
			positiveInfinitySymbol =      	"Infinity";
			positiveSign =                	"+";
			_nativeDigits = 				_NATIVE_DIGITS.concat();
			digitSubstitution = 			1;
			
			if (args.length == 1){
				var o:Object = args[0];
				for (var key:String in o){
					this[key] = o[key];
				} 
			}
		}

		public function getFormat(pType:*):* {
			if (pType is Number || pType is int || pType is uint) return this;
			return null;
		}
		
		public function get currencyDecimalDigits():int {
			return _currencyDecimalDigits;
		}
		
		public function set currencyDecimalDigits(pDigits:int):void {
			_checkRange(0, 99, pDigits);
			_currencyDecimalDigits = pDigits;
		}
		
		public function get currencyGroupSizes():Vector.<int> {
			return _currencyGroupSizes.concat();
		}
		
		public function set currencyGroupSizes(pSize:Vector.<int>):void {
			_currencyGroupSizes = _checkGroup(pSize);
		}
		
		public function get currencyNegativePattern():int {
			return _currencyNegativePattern;
		}
		
		public function set currencyNegativePattern(pPattern:int):void {
			_checkRange(0, 15, pPattern);
			_currencyNegativePattern = pPattern;
		}
		
		public function get currencyPositivePattern():int {
			return _currencyPositivePattern;
		}
		
		public function set currencyPositivePattern(pPattern:int):void {
			_checkRange(0, 3, pPattern);
			_currencyPositivePattern = pPattern;
		}
		
		public static function get currentInfo():NumberFormatInfo {
			return Culture.currentCulture.numberFormat;
		}
		
		public static function get invariantInfo():NumberFormatInfo {
			if (_invInfo == null){
				_invInfo = new NumberFormatInfo();
			}
			return _invInfo;
		}

		public function get nativeDigits():Vector.<int> {
			return _nativeDigits.concat();
		}
		
		public function set nativeDigits(pDigits:Vector.<int>):void {
			if (pDigits == null) throw new ArgumentError ("pDigits");
			if (pDigits.length != 10) throw new ArgumentError ("Argument array length must be 10");
			for each (var v:String in pDigits) {
				if (isNullOrEmpty(v)) throw new ArgumentError ("Argument array contains one or more null strings");
			}
			
			_nativeDigits = pDigits.concat();
		}
		
		public function get numberDecimalDigits():int {
			return _numberDecimalDigits;
		}
		
		public function set numberDecimalDigits(pDigits:int):void {
			_checkRange(0, 99, pDigits);
			_numberDecimalDigits = pDigits;
		}
		
		public function get numberGroupSizes():Vector.<int> {
			return _numberGroupSizes.concat();
		}
		
		public function set numberGroupSizes(pSize:Vector.<int>):void {
			_numberGroupSizes = _checkGroup(pSize);
		}
		
		public function get numberNegativePattern():int {
			return _numberNegativePattern;
		}
		
		public function set numberNegativePattern(pPattern:int):void {
			_checkRange(0, 4, pPattern);
			_numberNegativePattern = pPattern;
		}
		
		public function get percentDecimalDigits():int {
			return _percentDecimalDigits;
		}
		
		public function set percentDecimalDigits(pDigits:int):void {
			_checkRange(0, 99, pDigits);
			_percentDecimalDigits = pDigits;
		}
		
		public function get percentGroupSizes():Vector.<int> {
			return _percentGroupSizes.concat();
		}
		
		public function set percentGroupSizes(pSize:Vector.<int>):void {
			_percentGroupSizes = _checkGroup(pSize);
		}
		
		public function get percentNegativePattern():int {
			return _percentNegativePattern;
		}
		
		public function set percentNegativePattern(pPattern:int):void {
			_checkRange(0, 2, pPattern);
			_percentNegativePattern = pPattern;
		}
		
		public function get percentPositivePattern():int {
			return _percentPositivePattern;
		}
		
		public function set percentPositivePattern(pPattern:int):void {
			_checkRange(0, 2, pPattern);
			_percentPositivePattern = pPattern;
		}
		
		public static function getInstance(pProvider:IFormatProvider):NumberFormatInfo {
			if (pProvider != null) {
				var dtfi:NumberFormatInfo = NumberFormatInfo(pProvider.getFormat(0));//0 is a number...
				if (dtfi != null)
					return dtfi;
			}
			
			return currentInfo;
		}
		
		private function _checkRange(pMin:int, pMax:int, pVal:int):void {
			if (pVal < pMin || pVal > pMax) throw new RangeError(XString.format("The value specified for the property is less than {0} or greater than {1}", pMin, pMax));
		}
		
		private function _checkGroup(pVal:Vector.<int>):Vector.<int> {
			if (pVal == null) throw new ArgumentError("The value specified for the property is a null reference");
			if (pVal.length == 0) {
				return Vector.<int>([0]);
			}
			
			var last:int = pVal.length - 1;
			for (var i:int = 0; i<last; i++){
				if (pVal[i] < 1 || pVal[i] > 9) throw new RangeError ("One of the elements in the array specified is not between 1 and 9");
			}
			
			if (pVal[last] < 0 || pVal[last]) throw new RangeError ("Last element in the array specified is not between 0 and 9");
			
			return pVal.concat();
		}
	}
}
